/**
 * This file is part of Sonedyan.
 * 
 * Sonedyan is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public
 * License as published by the Free Software Foundation;
 * either version 3 of the License, or (at your option) any
 * later version.
 *
 * Sonedyan is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied
 * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
 * PURPOSE.  See the GNU General Public License for more
 * details.
 *
 * You should have received a copy of the GNU General Public
 * License along with Octave; see the file COPYING.  If not
 * see <http://www.gnu.org/licenses/>.
 * 
 * Copyright (C) 2009-2012 Jimmy Dubuisson <jimmy.dubuisson@gmail.com>
 */

package org.unige.mpej.eckmann.sonedyan.graph;

import java.awt.Color;
import java.text.SimpleDateFormat;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;

import org.unige.mpej.eckmann.sonedyan.bean.Edge;
import org.unige.mpej.eckmann.sonedyan.bean.Vertex;
import org.unige.mpej.eckmann.sonedyan.graph.GraphMetrics.GraphMetric;
import org.unige.mpej.eckmann.sonedyan.graph.GraphMetrics.NodeMetric;
import org.unige.mpej.eckmann.sonedyan.utils.GraphUtils;

import edu.uci.ics.jung.graph.Graph;

import org.apache.log4j.Logger;
import org.jfree.chart.ChartFactory;
import org.jfree.chart.ChartPanel;
import org.jfree.chart.JFreeChart;
import org.jfree.chart.axis.DateAxis;
import org.jfree.chart.plot.PlotOrientation;
import org.jfree.chart.plot.XYPlot;
import org.jfree.chart.title.LegendTitle;
import org.jfree.data.category.DefaultCategoryDataset;
import org.jfree.data.time.Month;
import org.jfree.data.time.TimeSeries;
import org.jfree.data.time.TimeSeriesCollection;
import org.jfree.data.xy.XYDataset;
import org.jfree.ui.ApplicationFrame;
import org.jfree.ui.RectangleInsets;
import org.jfree.ui.RefineryUtilities;
import org.jfree.util.UnitType;

/**
 * Graph drawing class
 */
public class GraphPlotter 
{
	private static Logger log = Logger.getLogger(org.unige.mpej.eckmann.sonedyan.graph.GraphPlotter.class);
	
	/**
	 * get degree distribution chart
	 */
	public static JFreeChart getDegreeDistributionChart(Map<Integer, Integer> map, String chartTitle, String xAxisTitle, String yAxisTitle)
	{
		if (map != null)
		{
			DefaultCategoryDataset dataset = new DefaultCategoryDataset();
			
			String series = "Series 1";
			
			for (int i = 0; i <= 10; i++)
			{
				Integer count = map.get(i);
				
				if (count == null){ count = 0; }
				
				dataset.addValue(count, series, new Integer(i).toString());
			}
			
			JFreeChart chart = ChartFactory.createBarChart(
					chartTitle, 
					xAxisTitle, 
					yAxisTitle,
					dataset, 
					PlotOrientation.VERTICAL, 
					false, // include legend 
					false, // include tooltips
					false // include URLs
				);
			
			return chart;
		}
		else
		{
			log.info("The distribution map is null");
			return null;
		}
	}
	
	/**
	 * get the graph metrics monthly time series for the specified algorithm
	 * 
	 * NB: filenames are formatted in the form M-YYYY.xml
	 */
	public static TimeSeries getGraphMetricsMonthlyTimeSeries(String[] filePaths, String timeSeriesTitle, GraphMetric metricType)
	{
		TimeSeries s = new TimeSeries(timeSeriesTitle);
		
		for (int i = 0; i < filePaths.length; i++)
		{
			String filePath = filePaths[i];
			Graph<Vertex, Edge> graph = GraphUtils.loadGraphMl(filePath);
			
			int slashPos = filePath.lastIndexOf("/");
			int dashPos = filePath.indexOf("-", slashPos);
			int dotPos = filePath.indexOf(".", slashPos);
			
			String monthString = filePath.substring(slashPos + 1, dashPos);
			String yearString = filePath.substring(dashPos + 1, dotPos);
			
			Month month = new Month(new Integer(monthString), new Integer(yearString));
			
			GraphMetrics<Vertex, Edge> graphMetrics = new GraphMetrics<Vertex, Edge>();
			
			s.add(month, graphMetrics.getGraphMetric(graph, metricType));
		}
		
		return s;
	}
	
	/**
	 * get the graph metrics normalized monthly time series for the specified algorithm
	 * 
	 * NB: filenames are formatted in the form M-YYYY.xml
	 */
	public static TimeSeries getNormalizedGraphMetricsMonthlyTimeSeries(String[] filePaths, String timeSeriesTitle, GraphMetric metricType)
	{
		TimeSeries s = new TimeSeries(timeSeriesTitle);
		HashMap<Month, Double> map = new HashMap<Month, Double>();
		double maxValue = 0;
		double minValue = 0;
		
		for (int i = 0; i < filePaths.length; i++)
		{
			String filePath = filePaths[i];
			Graph<Vertex, Edge> graph = GraphUtils.loadGraphMl(filePath);
			
			int slashPos = filePath.lastIndexOf("/");
			int dashPos = filePath.indexOf("-", slashPos);
			int dotPos = filePath.indexOf(".", slashPos);
			
			// filenames are formatted this way: 1-2000.xml
			String monthString = filePath.substring(slashPos + 1, dashPos);
			String yearString = filePath.substring(dashPos + 1, dotPos);
			
			Month month = new Month(new Integer(monthString), new Integer(yearString));
			
			double value = new GraphMetrics<Vertex, Edge>().getGraphMetric(graph, metricType);
			
			map.put(month, value);
			
			// initialize max and min values at the first iteration
			if (i == 0)
			{
				minValue = value;
				maxValue = value;
			}
			else
			{
				if (value > maxValue){ maxValue = value; }
				else if (value < minValue){ minValue = value; }
			}
		}
		
		Set<Month> keySet = map.keySet();
		
		for (Month month : keySet)
		{
			double value = map.get(month);
			double normalizedValue = (value - minValue) / (maxValue - minValue);
			
			// log.debug("Initial Value: '" + value + "' => Normalized value: '" + normalizedValue + "' ('" + minValue + "' - '" + maxValue + "')");
			
			s.add(month, normalizedValue);
		}
		
		return s;
	}
	
	/**
	 * get the normalized node metrics monthly time series for the specified algorithm
	 * 
	 * NB: filenames are formatted in the form M-YYYY.xml
	 */
	public static TimeSeries getNormalizedNodeMetricsMonthlyTimeSeries(String[] filePaths, String timeSeriesTitle, NodeMetric metricType, String nodeId)
	{
		TimeSeries s = new TimeSeries(timeSeriesTitle);
		HashMap<Month, Double> map = new HashMap<Month, Double>();
		double maxValue = 0;
		double minValue = 0;
		
		for (int i = 0; i < filePaths.length; i++)
		{
			String filePath = filePaths[i];
			Graph<Vertex, Edge> graph = GraphUtils.loadGraphMl(filePath);
			
			int slashPos = filePath.lastIndexOf("/");
			int dashPos = filePath.indexOf("-", slashPos);
			int dotPos = filePath.indexOf(".", slashPos);
			
			// filenames are formatted this way: 1-2000.xml
			String monthString = filePath.substring(slashPos + 1, dashPos);
			String yearString = filePath.substring(dashPos + 1, dotPos);
			
			Month month = new Month(new Integer(monthString), new Integer(yearString));
			
			double value = new GraphMetrics<Vertex, Edge>().getNodeMetric(graph, nodeId, metricType);
			
			map.put(month, value);
			
			// initialize max and min values at the first iteration
			if (i == 0)
			{
				minValue = value;
				maxValue = value;
			}
			else
			{
				if (value > maxValue){ maxValue = value; }
				else if (value < minValue){ minValue = value; }
			}
		}
		
		Set<Month> keySet = map.keySet();
		
		for (Month month : keySet)
		{
			double value = map.get(month);
			double normalizedValue = (value - minValue) / (maxValue - minValue);
			
			// log.debug("Initial Value: '" + value + "' => Normalized value: '" + normalizedValue + "' ('" + minValue + "' - '" + maxValue + "')");
			
			s.add(month, normalizedValue);
		}
		
		return s;
	}
	
	/**
	 * get the graph metrics monthly time series collection
	 */
	public static XYDataset getGraphMetricsMonthlyTimeSeriesCollection(String[] filePaths, Map<GraphMetric, String> metrics)
	{
		TimeSeriesCollection dataset = new TimeSeriesCollection();
		
		Set<GraphMetric> keySet = metrics.keySet();
		
		for (GraphMetric key : keySet)
		{
			TimeSeries s = getGraphMetricsMonthlyTimeSeries(filePaths, metrics.get(key), key);
			dataset.addSeries(s);
		}
		
		return dataset;
	}
	
	/**
	 * get the normalized graph metrics monthly time series collection
	 */
	public static XYDataset getNormalizedGraphMetricsMonthlyTimeSeriesCollection(String[] filePaths, Map<GraphMetric, String> metrics)
	{
		TimeSeriesCollection dataset = new TimeSeriesCollection();
		
		Set<GraphMetric> keySet = metrics.keySet();
		
		for (GraphMetric key : keySet)
		{
			TimeSeries s = getNormalizedGraphMetricsMonthlyTimeSeries(filePaths, metrics.get(key), key);
			dataset.addSeries(s);
		}
		
		return dataset;
	}
	
	/**
	 * get the normalized node metrics monthly time series collection
	 */
	public static XYDataset getNormalizedNodeMetricsMonthlyTimeSeriesCollection(String[] filePaths, Map<NodeMetric, String> metrics, String nodeId)
	{
		TimeSeriesCollection dataset = new TimeSeriesCollection();
		
		Set<NodeMetric> keySet = metrics.keySet();
		
		for (NodeMetric key : keySet)
		{
			TimeSeries s = getNormalizedNodeMetricsMonthlyTimeSeries(filePaths, metrics.get(key), key, nodeId);
			dataset.addSeries(s);
		}
		
		return dataset;
	}
	
	/**
	 * get graph metrics monthly time series chart
	 */
	public static JFreeChart getGraphMetricsMonthlyTimeSeriesChart(String[] filePaths, Map<GraphMetric, String> metrics, String chartTitle, String xAxisTitle, String yAxisTitle)
	{
		XYDataset dataset = getGraphMetricsMonthlyTimeSeriesCollection(filePaths, metrics);
		
		return getTimeSeriesChart(dataset, chartTitle, xAxisTitle, yAxisTitle);
	}
	
	/**
	 * get normalized graph metrics monthly time series chart
	 */
	public static JFreeChart getNormalizedGraphMetricsMonthlyTimeSeriesChart(String[] filePaths, Map<GraphMetric, String> metrics, String chartTitle, String xAxisTitle, String yAxisTitle)
	{
		XYDataset dataset = getNormalizedGraphMetricsMonthlyTimeSeriesCollection(filePaths, metrics);
		
		return getTimeSeriesChart(dataset, chartTitle, xAxisTitle, yAxisTitle);
	}
	
	/**
	 * get normalized node metrics monthly time series chart
	 */
	public static JFreeChart getNormalizedNodeMetricsMonthlyTimeSeriesChart(String[] filePaths, Map<NodeMetric, String> metrics, String nodeId, String chartTitle, String xAxisTitle, String yAxisTitle)
	{
		XYDataset dataset = getNormalizedNodeMetricsMonthlyTimeSeriesCollection(filePaths, metrics, nodeId);
		
		return getTimeSeriesChart(dataset, chartTitle, xAxisTitle, yAxisTitle);
	}
	
	/**
	 * get month time series chart
	 */
	public static JFreeChart getTimeSeriesChart(XYDataset dataset, String chartTitle, String xAxisTitle, String yAxisTitle)
	{
		if (dataset != null)
		{
			JFreeChart chart = ChartFactory.createTimeSeriesChart(
					chartTitle,
					xAxisTitle, 
					yAxisTitle,
					dataset,
					false,
					false,
					false
					);
			
			chart.setBackgroundPaint(Color.white);
			
			XYPlot plot = chart.getXYPlot();
			plot.setBackgroundPaint(Color.lightGray);
			plot.setDomainGridlinePaint(Color.white);
			plot.setRangeGridlinePaint(Color.white);
			plot.setAxisOffset(new RectangleInsets(UnitType.ABSOLUTE, 5.0, 5.0, 5.0, 5.0));
			plot.setDomainCrosshairVisible(true);
			plot.setRangeCrosshairVisible(true);
			
			chart.addLegend(new LegendTitle(plot));
			
			DateAxis axis = (DateAxis) plot.getDomainAxis();
			axis.setDateFormatOverride(new SimpleDateFormat("MMM-yyyy"));
			
			return chart;
		}
		else
		{
			log.info("The time series dataset is null");
			return null;
		}
	}
	
	/**
	 * get degree distribution chart
	 */
	public static <V extends Vertex, E extends Edge> JFreeChart getDegreeDistributionChart(Graph<V, E> graph)
	{
		Map<Integer, Integer> map = new GraphMetrics<V, E>().getDegreeDistribution(graph);
		
		return getDegreeDistributionChart(map, "Degree Distribution", "Degree", "# of Nodes");
	}
	
	/**
	 * get the degree distribution bar chart frame 
	 */
	public static <V extends Vertex, E extends Edge> void plotDegreeDistributionBarChartFrame(Graph<V, E> graph)
	{
		JFreeChart chart = getDegreeDistributionChart(graph);
		
		if (chart != null)
		{
			ApplicationFrame frame = new ApplicationFrame("Degree Distribution Bar Chart");
			
			// add the chart to a panel...
			ChartPanel chartPanel = new ChartPanel(chart);
			chartPanel.setPreferredSize(new java.awt.Dimension(500, 270));
			frame.setContentPane(chartPanel);
			
			frame.pack();
			
			RefineryUtilities.centerFrameOnScreen(frame);
			frame.setVisible(true);
		}
		else
		{
			log.info("Nothing to plot: the chart is null");
		}
	}
	
	/**
	 * plot the graph monthly time series
	 */
	public static void plotGraphMetricsMonthlyTimeSeriesChartFrame(String[] filePaths, Map<GraphMetric, String> metrics)
	{
		JFreeChart chart = getGraphMetricsMonthlyTimeSeriesChart(filePaths, metrics, "Monthly Graph Metrics", "Date", "Metric Value");
		
		if (chart != null)
		{
			ApplicationFrame frame = new ApplicationFrame("Graph Time Series Chart");
			
			ChartPanel chartPanel = new ChartPanel(chart);
			chartPanel.setPreferredSize(new java.awt.Dimension(800, 400));
			chartPanel.setMouseZoomable(true, false);
			frame.setContentPane(chartPanel);

			frame.pack();
			
			RefineryUtilities.centerFrameOnScreen(frame);
			frame.setVisible(true);
		}
		else
		{
			log.info("Nothing to plot: the chart is null");
		}
	}
	
	/**
	 * plot the normalized graph metrics monthly time series
	 */
	public static void plotNormalizedGraphMetricsMonthlyTimeSeriesChartFrame(String[] filePaths, Map<GraphMetric, String> metrics)
	{
		JFreeChart chart = getNormalizedGraphMetricsMonthlyTimeSeriesChart(filePaths, metrics, "Monthly Graph Metrics", "Date", "Normalized Metric Value");
		
		if (chart != null)
		{
			ApplicationFrame frame = new ApplicationFrame("Graph Time Series Chart");
			
			ChartPanel chartPanel = new ChartPanel(chart);
			chartPanel.setPreferredSize(new java.awt.Dimension(800, 600));
			chartPanel.setMouseZoomable(true, false);
			frame.setContentPane(chartPanel);

			frame.pack();
			
			RefineryUtilities.centerFrameOnScreen(frame);
			frame.setVisible(true);
		}
		else
		{
			log.info("Nothing to plot: the chart is null");
		}
	}
	
	/**
	 * plot the normalized node metrics monthly time series
	 */
	public static void plotNormalizedNodeMetricsMonthlyTimeSeriesChartFrame(String[] filePaths, Map<NodeMetric, String> metrics, String nodeId)
	{
		JFreeChart chart = getNormalizedNodeMetricsMonthlyTimeSeriesChart(filePaths, metrics, nodeId, "Monthly Node Metrics", "Date", "Normalized Metric Value");
		
		if (chart != null)
		{
			ApplicationFrame frame = new ApplicationFrame("Graph Time Series Chart");
			
			ChartPanel chartPanel = new ChartPanel(chart);
			chartPanel.setPreferredSize(new java.awt.Dimension(800, 600));
			chartPanel.setMouseZoomable(true, false);
			frame.setContentPane(chartPanel);

			frame.pack();
			
			RefineryUtilities.centerFrameOnScreen(frame);
			frame.setVisible(true);
		}
		else
		{
			log.info("Nothing to plot: the chart is null");
		}
	}
	
}
